import numpy as np
import logging
import time
import random
from multiprocessing import Pool
import os
# from sklearn.metrics import log_loss
from sklearn import metrics

import util.data_helper as data_helper

'''
算作对DE最终的尝试？
读取保存的概率文件，写一个很大很大的多项式，以最终的预测正确率为适应度值。
第一目标是测时间.
'''

logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# logger = logging.getLogger('master control system')     #
logger = logging.getLogger('吾将背水一战')     #
ROOT_NAME=int(time.time())
root = '../data/final/{}/'.format(ROOT_NAME)
if not os.path.exists(root):
	print('生成根目录：{}'.format(root))
	os.makedirs(root)

def read_origin_prob(file):

	origin_prob = []
	with open(file, 'r') as reader:
		for line in reader.readlines():
			temp = line.strip().split(',')
			probs = list(map(float, temp[0].split(' ')))
			# print(probs)
			origin_prob.append([probs, int(temp[1])])
	return origin_prob
# origin_prob = read_origin_prob('../data/final/origin_prob_0_1541236787.txt')
origin_prob = read_origin_prob('../data/final/origin_prob_8_1542199056.txt')
# preds = [origin_prob[i][0] for i in range(len(origin_prob))]
# labels = [origin_prob[i][1] for i in range(len(origin_prob))]

def read_origin_matrix(file):
	'''
	读取上一次的最优个体，初始种群将在这个最优解范围。
	:param file:
	:return:
	'''
	origin_matrix = []
	with open(file, 'r') as reader:
		for line in reader.readlines():
			origin_matrix.append(float(line.strip()))
	return origin_matrix
# origin_matrix = read_origin_matrix('../data/final/1542158773/optimal_cost_matrix.txt')
# print(origin_matrix)

def evolve(i, uCr, uF, N, Di, C,c_max, c_min, g, old_fitness, rand_i, best_c):
	logger.info('第{}个个体开始交叉：'.format(i))
	# population_without_i = np.delete(C, i, 0)  # 删掉了当代种群中的第i个个体(当前旧个体)
	# 首先随机调整每个个体的突变算子值和交叉概率值
	Cr_i = np.random.normal(uCr, 0.1)
	# 很尴尬,我找了几个常用的包,random,np.random,scipy.stats, 它们内部都没有柯西分布的实现
	# 全都是标准柯西分布, 比较尴尬, 我得自己实现是么...
	# warning！warning！warning！
	# 0.1为尺度参数scale，uF为位置参数location parameter
	# 2018-09-25 15:56:14 修改为非标准柯西分布完成，我是根据标准柯西分布和非标准柯西分布反函数公式的差别改造的
	# 理论上应该对，但是没有实战检验过其实。。。。。
	F_i = 0.1 * np.random.standard_cauchy() + uF  # 这里后续修改为柯西分布！！！！！也就是说这是个半成品。。。
	# 突变操作
	# 对当代种群的当前个体进行突变操作
	# indices = random.sample(range(N - 1), 2)  # 随机寻找两个切片
	indices = random.sample(range(N), 3)  # 随机寻找3个切片
	# if i in indices:  # 将3个删成2个，保证没有当前i即可
	# 	indices.remove(i)
	# else:
	# 	indices.pop(1)
	# ci_mutation = C[i] + F_i * (population_without_i[indices[0]] - population_without_i[indices[1]])
	# ci_mutation = C[i] + F_i * (C[indices[0]] - C[indices[1]])        # 原始的变异策略
	ci_mutation = C[indices[0]] + F_i * (C[indices[1]] - C[indices[2]])        # 标准DE的变异策略
	# ci_mutation = best_c + F_i * (C[indices[0]] - C[indices[1]])        # 新变异策略，将当前代最优个体加入迭代
	# ci_mutation = best_c + F_i * (C[indices[0]] + C[indices[1]] -C[indices[2]] -C[indices[3]] )        # 新变异策略，将当前代最优个体加入迭代
	# 变异操作后,突变中间体的基因可能会超过上下限区间,为了保证基因取值在区间之内,对超过区间外的值赋值为边界值
	# 如果超过边界, 取边界值
	# 检查处理上边界
	ci_mutation = [ci_mutation[item_1] if ci_mutation[item_1] < c_max[item_1] else c_max[item_1] for item_1 in range(Di)]
	# 检查并处理下边界
	ci_mutation = [ci_mutation[item_1] if ci_mutation[item_1] > c_min[item_1] else c_min[item_1] for item_1 in range(Di)]
	# 交叉操作,对变异后的个体,根据交叉概率与适应度来确定最后的个体

	ci_new = [ci_mutation[item_1] if i == rand_i or np.random.random() < Cr_i else C[i][item_1] for item_1 in range(Di)]
	# if i == rand_i or np.random.random() < Cr_i:
	# 	ci_new = ci_mutation
	# else:
	# 	ci_new = C[i]

	new_fitness = evaluate_fitness(g, i, D, ci_new)
	if old_fitness >= new_fitness:  # 同上一代个体的适应度函数作对比
		# C[i] = C[g][i]
		# pass  # 当前个体不变
		return new_fitness, []
	# 当前位置的fitness不变
	else:
		return new_fitness, [ci_new, Cr_i, F_i]

def captain(D=109, N=3, uF=0.5, uCr=0.5, beta=0.5, G=2, MODEL_NAME='final'):
	Di = D*(D-1)
	# 第二个维度为D-1是为了方便后来对角线位置插入0，作为正确分类的代价
	c_min = np.zeros(Di)
	c_max = np.ones(Di)
	pool_size = 1
	'''
	自适应差分进化算法的入口
	传入的维度是不是应该比正常的少个1，毕竟分对的那个是0
	在设置解空间维度的时候，有个地方需要注意，因为需要空出索引0和1的位置分别放置空开发者和无法解析的开发者位置，
	因此，维度D要在原先的基础上+2
	:param N: population size 种群大小
	:param D: Dimension size 解空间维度
	:param uF: mutation factors 突变算子
	:param uCr: Cross probability 交叉概率
	:param G: Generation 代数
	:param c_min: 染色体的最小值
	:param c_max: 
	:return: 
	'''
	all_start_time = time.time()
	fitness = [0 for _ in range(N)]  # 保留上轮迭代的fitness值，用来对比
	# 种群初始化
	# C = np.zeros((G, N, D*D))
	C = np.zeros((N, Di))  # 只保留一代，节省内存空间
	pool = Pool(pool_size)
	results = []
	for i in range(N):
		# 关于二维矩阵的乘法
		C[i] = c_min + np.random.random() * (c_max - c_min)  # 矩阵按位对应减
		# C[i] = np.random.uniform(low=0.8, high=1.0) * np.array(origin_matrix)
		# C[i] = np.random.random(size=( D*D))
		# fitness[i] = evaluate_fitness(C[0][i], D)
		results.append(pool.apply_async(evaluate_fitness, args=(0, i, D, C[i])))
	# logger.info('初始种群第{}号个体的fitness为{}.'.format(i, fitness[i]))
	pool.close()
	pool.join()
	# 得到子进程的输出
	for i in range(len(results)):
		# print('result：{}'.format(results[i].get()))
		fitness[i] = results[i].get()
	print('差分进化种群初始化完成!耗时为：{}'.format(time.time() - all_start_time))

	optimal_index = np.argmax(fitness)
	logger.info('当前种群最佳个体的适应度值：{}'.format(np.max(fitness)))
	optimal_cost_matrix = C[optimal_index]  # 取子代的最优代价组合
	# 将最优代价矩阵保留
	# 此刻保存的矩阵是没有加工过的，即对角线没有归0的那种
	with open('../data/{}/{}/{}_optimal_cost_matrix.txt'.format(MODEL_NAME, ROOT_NAME, 'init'), 'w') as writer:
		for i in range(len(optimal_cost_matrix)):
			writer.write('{}\n'.format(optimal_cost_matrix[i]))
	generation_fitness = []         # 记录每一代的最大适应度值
	# 循环代数
	for g in range(G - 1):
		S_Cr = []  # 保存所有成功的交叉概率,意思是, 成功交叉的样本中的交叉概率
		S_F = []  # 同上
		# print('当前位于第{}代'.format(g))
		logger.info('当前位于第{}代'.format(g + 1))

		# temp_Cc = []
		temp_Cr = []
		temp_F = []
		pool = Pool(pool_size)
		results = []
		for i in range(N):  # 循环种群内所有个体
			rand_i = np.random.randint(1, Di)  # 保证每次必定有一个突变基因会遗传给下一代
			results.append(pool.apply_async(evolve, args=(i, uCr, uF, N, Di, C,c_max, c_min, g, fitness[i], rand_i, C[np.argmax(fitness)])))
		pool.close()
		pool.join()
		for i in range(len(results)):
			# print('result：{}'.format(results[i].get()))
			# new_fitness, ci_new, Cr_i, F_i = results[i].get()
			new_fitness, values = results[i].get()
			# if fitness[i] > new_fitness:  # 同上一代个体的适应度函数作对比
			if len(values) == 0:  # 同上一代个体的适应度函数作对比
				# C[i] = C[g][i]
				pass  # 当前个体不变
				# 当前位置的fitness不变
			else:
				ci_new, Cr_i, F_i = values
				C[i] = ci_new  # 交叉成功,保留其相关算子
				S_Cr.append(Cr_i)
				S_F.append(F_i)
				# 当前位置的fitness改变
				logger.info('选择成功，从适应度{}提升至{}'.format(fitness[i], new_fitness))
				fitness[i] = new_fitness

			# temp_Cr.append(Cr_i)
			# temp_F.append(F_i)
			# temp_Cc.append(ci_new)
		# 适应度评价
		# if evaluate_fitness(C[g][i]) > evaluate_fitness(ci_new):
		# pool = Pool(pool_size)
		# results = []
		# for i in range(N):
		# 	results.append(pool.apply_async(evaluate_fitness, args=(g, i, D, temp_Cc[i])))
		# new_fitness = evaluate_fitness(ci_new, D)
		# pool.close()
		# pool.join()
		# 开始进行当代所有个体的选择和后代择优
		# for i in range(N):
		# 	new_fitness = results[i].get()
		# 	if fitness[i] > new_fitness:  # 同上一代个体的适应度函数作对比
		# 		# C[i] = C[g][i]
		# 		pass  # 当前个体不变
		# 	# 当前位置的fitness不变
		# 	else:
		# 		C[i] = temp_Cc[i]  # 交叉成功,保留其相关算子
		# 		S_Cr.append(temp_Cr[i])
		# 		S_F.append(temp_F[i])
		# 		# 当前位置的fitness改变
		# 		logger.info('选择成功，从适应度{}提升至{}'.format(fitness[i], new_fitness))
		# 		fitness[i] = new_fitness
		# 在每轮迭代结束,参数自适应调整

		uCr = (1 - beta) * uCr + beta * (sum(S_Cr) / (len(S_Cr) if len(S_Cr) != 0 else 1))
		uF = (1 - beta) * uF + beta * (sum(S_F) / (len(S_F) if len(S_F) != 0 else 1))
		logger.info("第{}代种群已产生.".format(g + 1))

		# 每一次进化完毕之后，取当前子代最优的代价矩阵
		optimal_index = np.argmax(fitness)
		optimal_fitness = np.max(fitness)
		logger.info('当前种群最佳个体的适应度值：{}'.format(optimal_fitness))
		generation_fitness.append(optimal_fitness)      # 记录每一代的最佳适应度值
		optimal_cost_matrix = C[optimal_index]  # 取子代的最优代价组合
		# 将最优代价矩阵保留
		# 此刻保存的矩阵是没有加工过的，即对角线没有归0的那种
		with open('../data/{}/{}/optimal_cost_matrix.txt'.format(MODEL_NAME, ROOT_NAME), 'w') as writer:
			for i in range(len(optimal_cost_matrix)):
				writer.write('{}\n'.format(optimal_cost_matrix[i]))
		# 在每轮进化完毕之后，下轮进化之前，保存现场，比如说每个个体的数据,自适应参数值
		with open('../data/{}/{}/scene_data.txt'.format(MODEL_NAME, ROOT_NAME), 'w') as writer:
			writer.write('uCr={}, uF={}\n'.format(str(uCr), str(uF)))
			for i in range(N):
				writer.write('{}\n'.format(' '.join([str(C[i][j]) for j in range(Di)])))
				writer.write('{}\n'.format(fitness[i]))
		# 将每一代中的最佳适应度值追加保存至文件
		with open(os.path.join(root, 'fitnesses.txt'), 'a') as writer:
			writer.write('{}\n'.format(optimal_fitness))
	# 寻找最后一代中适应度最佳的个体,为了节省时间,我应该需要记录每一轮所有个体的适应度值,方便selection操作时候进行比较
	# 也方便在最后一代中寻找最佳值
	print('整个实验最终耗时为：{}'.format(time.time() - all_start_time))
	# 将所有代中的最佳适应度值保存至文件
	# with open(os.path.join(root, 'fitnesses.txt'), 'w') as writer:
	# 	[writer.write('{}\n'.format(generation_fitness[i])) for i in range(len(generation_fitness))]
	# 种群迭代完成
	# 保留最佳，并且注意输出
	# optimal_index = np.argmax(fitness)
	# logger.info('当前种群最佳个体的适应度值：{}'.format(np.max(fitness)))
	# optimal_cost_matrix = C[-1][optimal_index]  # 取最后一代
	# # 将最优代价矩阵保留
	# # 此刻保存的矩阵是没有加工过的，即对角线没有归0的那种
	# with open('../data/optimal_cost_matrix.txt', 'w') as writer:
	# 	for i in range(len(optimal_cost_matrix)):
	# 		writer.write('{}\n'.format(optimal_cost_matrix[i]))


def evaluate_fitness(g, s, D, x):
	# logger.info('now, generation={}, individual={}'.format(g, s))
	# print(D)
	# cost_matrix = np.array([[0 for _ in range(D)] for _ in range(D)])
	# cost_matrix = np.reshape(x, D)
	cost_matrix = np.array(x).reshape((D, D-1)).tolist()
	for i in range(D):
		cost_matrix[i].insert(i, 0)       # 对角线元素为0
	cost_matrix = np.array(cost_matrix)
	top1_num = 0
	# top5_num = 0
	# 计算多项式的值,以top1作为评价标准

	# punished_predicts = []
	# true_label = []
	for i in range(len(origin_prob)):
		label = origin_prob[i][1]
		# origin_prob[i][0][label](1-cost_matrix)[:,label]
		# prob_top_5 = np.sort(origin_prob[i][0])[-5:]
		# index_top_5 = np.argsort(origin_prob[i][0])[-5:]        # 在top5的基础上，进一步提高top1。
		# punished_prob = np.sum(np.multiply(origin_prob[i][0], cost_matrix), axis=0) # 对原始概率进行惩罚, 加起来大于1了，这个需要改动，改成以代价为主？
		# punished_prob = np.multiply(origin_prob[i][0], 1-cost_matrix) # 对原始概率进行惩罚, 加起来大于1了，这个需要改动，改成以代价为主？
		punished_prob = np.matmul(origin_prob[i][0], 1-cost_matrix) # 对原始概率进行惩罚, 加起来大于1了，这个需要改动，改成以代价为主？
		# punished_prob = [punished_prob[j] for j in index_top_5]
		# print(punished_prob)
		# punished_prob = punished_prob/np.sum(punished_prob)         # 再次归一化
		# one_labels = np.zeros(shape=D)
		# one_labels[label] = 1
		# loss += (-np.sum(np.multiply(one_labels, np.log(punished_prob))))        # 手动计算交叉熵
		# print(costs)
		# 分别计算top1和top5
		# top_index = np.argsort(punished_prob)       # 根据惩罚之后的值,升序排列索引
		top_index = np.argmax(punished_prob)       # 根据惩罚之后的值,升序排列索引, 最大值可以选择出来啊，但是可能是因为最小值太多了，最小值有很多个？
		# true_label.append(label)
		# punished_predicts.append(top_index)
		if label == top_index:
		# if label == index_top_5[top_index]:
			top1_num += 1
		# if label in top_index[-5:]:
		# 	top5_num += 1
	# 当预测的值不包含实际值会报错UndefinedMetricWarning
	# top1 = metrics.precision_score(true_label, punished_predicts, average='macro', labels=list(range(D)))     # 计算精确率的宏平均
	top1 = top1_num/len(origin_prob)
	# top5 = top5_num/len(origin_prob)
	# logger.info('finish, generation={}, individual={}, acc={}'.format(g, s, loss))
	logger.info('finish, generation={}, individual={}, acc={}'.format(g, s, top1))
	# logger.info('finish, generation={}, individual={}, acc={}'.format(g, s, top5))
	# return loss      # 返回top1的正确率
	return top1        # 返回top1的正确率
	# return top5        # 返回top1的正确率

if __name__ == '__main__':
	developers_list = data_helper.create_developers_list()
	D = len(developers_list)
	# D=642
	# captain(D=D, N=10, G=500)
	captain(D=D, N=300, G=5000)
	# x = np.random.random(size=[D*D])
	# top1 = evaluate_fitness(1, 1, D, x)
	# print(top1)
